﻿namespace Spectral
{
    using System;
    using Spectral.Operations;

    /// <summary>
    /// Provides creational methods for matrices.
    /// </summary>
    public static class Matrix
    {
        #region Create
        /// <summary>
        /// Creates a 2x2 matrix.
        /// </summary>
        /// <typeparam name="T">The element type.</typeparam>
        /// <param name="m11">The element at [1, 1].</param>
        /// <param name="m12">The element at [1, 2].</param>
        /// <param name="m21">The element at [2, 1].</param>
        /// <param name="m22">The element at [2, 2].</param>
        /// <returns>A matrix.</returns>
        public static Matrix2x2<T> Create<T>(T m11, T m12, T m21, T m22)
            where T : struct, IEquatable<T>, IComparable<T>
        {
            return new Matrix2x2<T>(m11, m12, m21, m22);
        }

        /// <summary>
        /// Creates a 3x3 matrix.
        /// </summary>
        /// <typeparam name="T">The element type.</typeparam>
        /// <param name="m11">The element at [1, 1].</param>
        /// <param name="m12">The element at [1, 2].</param>
        /// <param name="m13">The element at [1, 3].</param>
        /// <param name="m21">The element at [2, 1].</param>
        /// <param name="m22">The element at [2, 2].</param>
        /// <param name="m23">The element at [2, 3].</param>
        /// <param name="m31">The element at [3, 1].</param>
        /// <param name="m32">The element at [3, 2].</param>
        /// <param name="m33">The element at [3, 3].</param>
        /// <returns>A matrix.</returns>
        public static Matrix3x3<T> Create<T>(T m11, T m12, T m13, T m21, T m22, T m23, T m31, T m32, T m33)
            where T : struct, IEquatable<T>, IComparable<T>
        {
            return new Matrix3x3<T>(m11, m12, m13, m21, m22, m23, m31, m32, m33);
        }

        /// <summary>
        /// Creates a 4x4 matrix.
        /// </summary>
        /// <typeparam name="T">The element type.</typeparam>
        /// <param name="m11">The element at [1, 1].</param>
        /// <param name="m12">The element at [1, 2].</param>
        /// <param name="m13">The element at [1, 3].</param>
        /// <param name="m14">The element at [1, 4].</param>
        /// <param name="m21">The element at [2, 1].</param>
        /// <param name="m22">The element at [2, 2].</param>
        /// <param name="m23">The element at [2, 3].</param>
        /// <param name="m24">The element at [2, 4].</param>
        /// <param name="m31">The element at [3, 1].</param>
        /// <param name="m32">The element at [3, 2].</param>
        /// <param name="m33">The element at [3, 3].</param>
        /// <param name="m34">The element at [3, 4].</param>
        /// <param name="m41">The element at [4, 1].</param>
        /// <param name="m42">The element at [4, 2].</param>
        /// <param name="m43">The element at [4, 3].</param>
        /// <param name="m44">The element at [4, 4].</param>
        /// <returns>A matrix.</returns>
        public static Matrix4x4<T> Create<T>(T m11, T m12, T m13, T m14, T m21, T m22, T m23, T m24, T m31, T m32, T m33, T m34, T m41, T m42, T m43, T m44)
            where T : struct, IEquatable<T>, IComparable<T>
        {
            return new Matrix4x4<T>(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44);
        }
        #endregion

        #region CreateScale
        /// <summary>
        /// Creates a scaling matrix.
        /// </summary>
        /// <typeparam name="T">The element type.</typeparam>
        /// <param name="x">The x scaling factor.</param>
        /// <param name="y">The y scaling factor.</param>
        /// <returns>A matrix.</returns>
        public static Matrix3x3<T> CreateScale<T>(T x, T y)
            where T : struct, IEquatable<T>, IComparable<T>
        {
            return new Matrix3x3<T>(
                x, Operator.Zero<T>(), Operator.Zero<T>(),
                Operator.Zero<T>(), y, Operator.Zero<T>(),
                Operator.Zero<T>(), Operator.Zero<T>(), Operator.One<T>());
        }

        /// <summary>
        /// Creates a translation matrix.
        /// </summary>
        /// <typeparam name="T">The element type.</typeparam>
        /// <param name="x">The x scaling factor.</param>
        /// <param name="y">The y scaling factor.</param>
        /// <param name="z">The z scaling factor.</param>
        /// <returns>A matrix.</returns>
        public static Matrix4x4<T> CreateScale<T>(T x, T y, T z)
            where T : struct, IEquatable<T>, IComparable<T>
        {
            return new Matrix4x4<T>(
                x, Operator.Zero<T>(), Operator.Zero<T>(), Operator.Zero<T>(),
                Operator.Zero<T>(), y, Operator.Zero<T>(), Operator.Zero<T>(),
                Operator.Zero<T>(), Operator.Zero<T>(), z, Operator.Zero<T>(),
                Operator.Zero<T>(), Operator.Zero<T>(), Operator.Zero<T>(), Operator.One<T>());
        }
        #endregion

        #region CreateRotation
        /// <summary>
        /// Creates a rotation matrix.
        /// </summary>
        /// <typeparam name="T">The element type.</typeparam>
        /// <param name="value">The value.</param>
        /// <returns>A matrix.</returns>
        public static Matrix3x3<T> CreateRotation<T>(T value)
            where T : struct, IEquatable<T>, IComparable<T>
        {
            var cos = value.Cos();
            var sin = value.Sin();
            return new Matrix3x3<T>(
                cos, sin, Operator.Zero<T>(),
                sin.Negate(), cos, Operator.Zero<T>(),
                Operator.Zero<T>(), Operator.Zero<T>(), Operator.One<T>());
        }

        /// <summary>
        /// Creates a rotation matrix around the x-axis.
        /// </summary>
        /// <typeparam name="T">The element type.</typeparam>
        /// <param name="value">The value.</param>
        /// <returns>A matrix.</returns>
        public static Matrix4x4<T> CreateRotationX<T>(T value)
            where T : struct, IEquatable<T>, IComparable<T>
        {
            var cos = value.Cos();
            var sin = value.Sin();
            return new Matrix4x4<T>(
                Operator.One<T>(), Operator.Zero<T>(), Operator.Zero<T>(), Operator.Zero<T>(),
                Operator.Zero<T>(), cos, sin, Operator.Zero<T>(),
                Operator.Zero<T>(), sin.Negate(), cos, Operator.Zero<T>(),
                Operator.Zero<T>(), Operator.Zero<T>(), Operator.Zero<T>(), Operator.One<T>());
        }

        /// <summary>
        /// Creates a rotation matrix around the y-axis.
        /// </summary>
        /// <typeparam name="T">The element type.</typeparam>
        /// <param name="value">The value.</param>
        /// <returns>A matrix.</returns>
        public static Matrix4x4<T> CreateRotationY<T>(T value)
            where T : struct, IEquatable<T>, IComparable<T>
        {
            var cos = value.Cos();
            var sin = value.Sin();
            return new Matrix4x4<T>(
                cos, Operator.Zero<T>(), sin.Negate(), Operator.Zero<T>(),
                Operator.Zero<T>(), Operator.One<T>(), Operator.Zero<T>(), Operator.Zero<T>(),
                sin, Operator.Zero<T>(), cos, Operator.Zero<T>(),
                Operator.Zero<T>(), Operator.Zero<T>(), Operator.Zero<T>(), Operator.One<T>());
        }

        /// <summary>
        /// Creates a rotation matrix around the z-axis.
        /// </summary>
        /// <typeparam name="T">The element type.</typeparam>
        /// <param name="value">The value.</param>
        /// <returns>A matrix.</returns>
        public static Matrix4x4<T> CreateRotationZ<T>(T value)
            where T : struct, IEquatable<T>, IComparable<T>
        {
            var cos = value.Cos();
            var sin = value.Sin();
            return new Matrix4x4<T>(
                cos, sin, Operator.Zero<T>(), Operator.Zero<T>(),
                sin.Negate(), cos, Operator.Zero<T>(), Operator.Zero<T>(),
                Operator.Zero<T>(), Operator.Zero<T>(), Operator.One<T>(), Operator.Zero<T>(),
                Operator.Zero<T>(), Operator.Zero<T>(), Operator.Zero<T>(), Operator.One<T>());
        }
        #endregion

        #region CreateTranslation
        /// <summary>
        /// Creates a translation matrix.
        /// </summary>
        /// <typeparam name="T">The element type.</typeparam>
        /// <param name="value">The value.</param>
        /// <returns>A matrix.</returns>
        public static Matrix3x3<T> CreateTranslation<T>(Vector2<T> value)
            where T : struct, IEquatable<T>, IComparable<T>
        {
            return CreateTranslation(value.X, value.Y);
        }

        /// <summary>
        /// Creates a translation matrix.
        /// </summary>
        /// <typeparam name="T">The element type.</typeparam>
        /// <param name="x">The x coordinate.</param>
        /// <param name="y">The y coordinate.</param>
        /// <returns>A matrix.</returns>
        public static Matrix3x3<T> CreateTranslation<T>(T x, T y)
            where T : struct, IEquatable<T>, IComparable<T>
        {
            return new Matrix3x3<T>(
                Operator.One<T>(), Operator.Zero<T>(), Operator.Zero<T>(),
                Operator.Zero<T>(), Operator.One<T>(), Operator.Zero<T>(),
                x, y, Operator.One<T>());
        }

        /// <summary>
        /// Creates a translation matrix.
        /// </summary>
        /// <typeparam name="T">The element type.</typeparam>
        /// <param name="value">The value.</param>
        /// <returns>A matrix.</returns>
        public static Matrix4x4<T> CreateTranslation<T>(Vector3<T> value)
            where T : struct, IEquatable<T>, IComparable<T>
        {
            return CreateTranslation(value.X, value.Y, value.Z);
        }

        /// <summary>
        /// Creates a translation matrix.
        /// </summary>
        /// <typeparam name="T">The element type.</typeparam>
        /// <param name="x">The x coordinate.</param>
        /// <param name="y">The y coordinate.</param>
        /// <param name="z">The z coordinate.</param>
        /// <returns>A matrix.</returns>
        public static Matrix4x4<T> CreateTranslation<T>(T x, T y, T z)
            where T : struct, IEquatable<T>, IComparable<T>
        {
            return new Matrix4x4<T>(
                Operator.One<T>(), Operator.Zero<T>(), Operator.Zero<T>(), Operator.Zero<T>(),
                Operator.Zero<T>(), Operator.One<T>(), Operator.Zero<T>(), Operator.Zero<T>(),
                Operator.Zero<T>(), Operator.Zero<T>(), Operator.One<T>(), Operator.Zero<T>(),
                x, y, z, Operator.One<T>());
        }
        #endregion

        #region CreatePerspective
        /// <summary>
        /// Creates a perspective matrix.
        /// </summary>
        /// <typeparam name="T">The element type.</typeparam>
        /// <param name="left">The left plane.</param>
        /// <param name="right">The right plane.</param>
        /// <param name="top">The top plane.</param>
        /// <param name="bottom">The bottom plane.</param>
        /// <param name="near">The near plane.</param>
        /// <param name="far">The far plane.</param>
        /// <returns>A matrix.</returns>
        public static Matrix4x4<T> CreatePerspective<T>(T left, T right, T top, T bottom, T near, T far)
            where T : struct, IEquatable<T>, IComparable<T>
        {
            var twoNear = near.Add(near);
            var rightMinusLeft = right.Subtract(left);
            var topMinusBottom= top.Subtract(bottom);
            var nearMinusFar = near.Subtract(far);

            return new Matrix4x4<T>(
                twoNear.Divide(rightMinusLeft), Operator.Zero<T>(), Operator.Zero<T>(), Operator.Zero<T>(),
                Operator.Zero<T>(), twoNear.Divide(topMinusBottom), Operator.Zero<T>(), Operator.Zero<T>(),
                right.Add(Operator.One<T>()).Divide(rightMinusLeft), top.Add(bottom).Divide(topMinusBottom), far.Add(near).Divide(nearMinusFar), Operator.One<T>().Negate(),
                Operator.Zero<T>(), Operator.Zero<T>(), twoNear.Multiply(far).Divide(nearMinusFar), Operator.Zero<T>());
        }
        #endregion
    }
}
